package org.blue.eventbus;

import java.lang.reflect.Method;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;

import org.blue.eventbus.annotation.Recieve;

class AnnotationManager {

	private final ConcurrentMap<Class<?>, Map<Class<?>, Set<Method>>> RECIEVERS_CACHE = new ConcurrentHashMap<>();

	void loadAnnotatedMethods(Class<?> targetClass) {
		Method[] methods = targetClass.getDeclaredMethods();
		for (Method method : methods) {
			if (method.isAnnotationPresent(Recieve.class)) {
				Class<?>[] declaredEventTypes = method.getParameterTypes();
				if (declaredEventTypes.length != 1) {
					throw new IllegalStateException("Could not resolve declared event type");
				}
				Class<?> declaredEventType = declaredEventTypes[0];

				Recieve annotation = method.getAnnotation(Recieve.class);
				Class<?>[] eventTypes = annotation.eventTypes();

				Map<Class<?>, Set<Method>> typeMethodsMap = RECIEVERS_CACHE.get(targetClass);
				if (typeMethodsMap == null) {
					typeMethodsMap = new HashMap<>();
					RECIEVERS_CACHE.put(targetClass, typeMethodsMap);
				}
				for (Class<?> eventType : eventTypes) {
					if (!declaredEventType.isAssignableFrom(eventType)) {
						throw new IllegalStateException("Declared event type mismatch");
					}
					Set<Method> methodSet = typeMethodsMap.get(eventType);
					if (methodSet == null) {
						methodSet = new HashSet<>();
						typeMethodsMap.put(eventType, methodSet);
					}
					methodSet.add(method);
				}
			}
		}
	}

	Map<Class<?>, Set<AnnotatedReceiver>> findAllRecievers(Object target) {
		Map<Class<?>, Set<Method>> typeMethodsMap = RECIEVERS_CACHE.get(target.getClass());
		if (typeMethodsMap == null) {
			loadAnnotatedMethods(target.getClass());
			typeMethodsMap = RECIEVERS_CACHE.get(target.getClass());
		}
		Map<Class<?>, Set<AnnotatedReceiver>> typeCtxMap = new HashMap<>();
		for (Map.Entry<Class<?>, Set<Method>> typeMethodsEntry : typeMethodsMap.entrySet()) {
			Set<AnnotatedReceiver> ctxSet = new HashSet<AnnotatedReceiver>();
			typeCtxMap.put(typeMethodsEntry.getKey(), ctxSet);
			for (Method method : typeMethodsEntry.getValue()) {
				ctxSet.add(new AnnotatedReceiver(target, method));
			}
		}
		return typeCtxMap;
	}
}
